<?php
declare(strict_types=1);

namespace LiLei\MyBaiGoSso;

class MyBaiGoSso
{
    // BaiGoSso 创建应用后生成的自增表 ID
    public $app_id;
    // BaiGoSso key
    public $app_key;
    // BaiGoSso 密钥
    public $app_secret;
    // BaiGoSso 服务地址 URL
    public $url;
    // BaiGoSso 服务地址 URI
    public $uri;

    // curl 访问超时
    const CURL_TIMEOUT = 3;
    // openssl 加密方式
    const OPENSSL_METHOD = 'AES-128-CBC';
    // openssl 参数选项
    const OPENSSL_OPTIONS = 1;

    // http://xxx/index.php/console/login/ 登录 BaiGoSso 后台
    // BaiGoSso api 列表，根据自身的地址进行替换成属于项目里的 BaiGoSso 的访问地址
    const URIS = [
        'register'          => '/index.php/api/reg/reg',      // 用户注册
        'check_name'        => '/index.php/api/reg/chkname',  // 检查用户名
        'login'             => '/index.php/api/login/login',  // 用户登录
        'refresh_token'     => '/index.php/api/profile/token',// 刷新访问口令
        'reset_password'    => '/index.php/api/profile/pass', // 修改密码
        'user_info'         => '/index.php/api/user/read',    // 读取用户数据
        'sync_login'        => '/index.php/api/sync/login',   // 同步登录
        'sync_logout'       => '/index.php/api/sync/logout',  // 同步登出
    ];

    /**
     * MyBaiGoSso constructor.
     * @param int    $app_id
     * @param string $app_key
     * @param string $app_secret
     * @param string $url
     */
    public function __construct(int $app_id, string $app_key, string $app_secret, string $url)
    {
        $this->app_id       = $app_id;
        $this->app_key      = $app_key;
        $this->app_secret   = $app_secret;
        $this->url          = $url;
    }

    /**
     * 数据处理
     *
     * @param array $form_data  请求数据
     * @return array
     */
    public function handleData(array $form_data = []): array
    {
        $json = json_encode($form_data, JSON_UNESCAPED_UNICODE);
        $code = $this->encrypt($json);
        $sign = $this->generateSign($json);
        //dump($json);
        //dump($code);
        //dump($sign);

        return [$code, $sign];
    }// handleData() end

    /**
     * 处理返回数据
     *
     * @param array $response
     * @return array
     */
    public function handleResponse(array $response = [])
    {
        if (empty($response) || empty($response['rcode'])) return ['code' => 0, 'message' => $response['rcode']];

        if (substr($response['rcode'], 0, 1) !== 'y') return ['code' => 0, 'message' => $response['rcode']];

        return ['code' => 1, 'message' => $response['rcode']];
    }// handleResponse() end

    /**
     * 用户注册
     *
     * @param string $username
     * @param string $password
     * @return mixed
     */
    public function register(string $username, string $password)
    {
        // post
        $form_data = [
            'user_name' => $username,
            'user_pass' => $password,
            'timestamp' => time(),
        ];
        [$code, $sign] = $this->handleData($form_data);
        $response      = $this->send($code, $sign);

        return $response;
    }// register() end

    /**
     * 检查用户名
     *
     * @param string $username
     * @return mixed
     */
    public function checkName(string $username)
    {
        // get
        $form_data = [
            'user_name' => $username,
            'timestamp' => time()
        ];
        [$code, $sign] = $this->handleData($form_data);
        $response      = $this->send($code, $sign);
        //dump($response);

        return $this->handleResponse($response);
    }// checkName() end

    /**
     * 用户登录
     *
     * @param string $username
     * @param string $password
     * @return mixed
     */
    public function login(string $username, string $password)
    {
        // post
        $form_data = [
            'user_name' => $username,
            'user_pass' => $password,
            'timestamp' => time()
        ];
        [$code, $sign] = $this->handleData($form_data);
        $response      = $this->send($code, $sign);

        return $response;
    }// login() end

    /**
     * 刷新访问口令
     *
     * @param int    $user_id
     * @param string $user_refresh_token
     * @return mixed
     */
    public function refreshToken(int $user_id, string $user_refresh_token)
    {
        // post
        $form_data = [
            'user_id'               => $user_id,
            'user_refresh_token'    => $user_refresh_token,
            'timestamp'             => time()
        ];
        [$code, $sign] = $this->handleData($form_data);
        $response      = $this->send($code, $sign);

        return $response;
    }// refreshToken() end

    /**
     * 修改密码
     *
     * @param int    $user_id
     * @param string $oldPassword
     * @param string $newPassword
     * @return mixed
     */
    public function resetPassword(int $user_id, string $oldPassword, string $newPassword)
    {
        // post
        $form_data = [
            'user_id'       => $user_id,
            'user_pass'     => $oldPassword,
            'user_pass_new' => $newPassword,
            'timestamp'     => time()
        ];
        [$code, $sign] = $this->handleData($form_data);
        $response      = $this->send($code, $sign);

        return $response;
    }// resetPassword() end

    /**
     * 读取用户数据
     *
     * @param int $user_id
     * @return mixed
     */
    public function getUserInfo(int $user_id)
    {
        // get
        $form_data = [
            'user_id'   => $user_id,
            'timestamp' => time()
        ];
        [$code, $sign] = $this->handleData($form_data);
        $response      = $this->send($code, $sign);

        return $response;
    }// getUserInfo() end

    /**
     * 同步登录
     *
     * @param int    $user_id
     * @param string $user_access_token
     * @return mixed
     */
    public function syncLogin(int $user_id, string $user_access_token)
    {
        // post
        $form_data = [
            'user_id'           => $user_id,
            'user_access_token' => $user_access_token,
            'timestamp'         => time()
        ];
        [$code, $sign] = $this->handleData($form_data);
        $response      = $this->send($code, $sign);

        return $response;
    }// syncLogin() end

    /**
     * 同步登出
     *
     * @param int    $user_id
     * @param string $user_access_token
     * @return mixed
     */
    public function syncLogout(int $user_id, $user_access_token)
    {
        // post
        $form_data = [
            'user_id'           => $user_id,
            'user_access_token' => $user_access_token,
            'timestamp'         => time()
        ];
        [$code, $sign] = $this->handleData($form_data);
        $response      = $this->send($code, $sign);

        return $response;
    }// syncLogout() end

    // 同步通知 url 数据处理
    public function notify(string $code): array
    {
        $data = $this->decrypt($code);
        if (empty($data)) return [];

        return json_decode($data, true);
    }// notify() end

    // 发送请求
    public function send(string $code, string $sign)
    {
        $data    = [
            'code'      => $code,
            'sign'      => $sign,
            'app_id'    => $this->app_id,
            'app_key'   => $this->app_key
        ];
        $headers = [
            "Content-type:application/x-www-form-urlencoded;charset='utf-8'",
            "Accept:application/json"
        ];
        $options = [
            CURLOPT_URL             => $this->url . $this->uri,
            CURLOPT_SSL_VERIFYPEER  => false,
            CURLOPT_SSL_VERIFYHOST  => false,
            CURLOPT_POST            => 1,
            CURLOPT_POSTFIELDS      => http_build_query($data),
            CURLOPT_HTTPHEADER      => $headers,
            CURLOPT_RETURNTRANSFER  => 1,
            CURLOPT_TIMEOUT         => self::CURL_TIMEOUT,
        ];

        $curl = curl_init();
        curl_setopt_array($curl, $options);

        try {
            $response = curl_exec($curl);
        } catch (\Exception $e) {
            $response = '';
        }
        curl_close($curl);
        if (is_string($response) === false) $response = '';

        return json_decode($response, true);
    }// send() end

    /**
     * 获取属性值
     *
     * @param string $name
     * @return string
     */
    public function getAttribute(string $name): string
    {
        if (strlen($name) > 0) $name = strtolower($name);

        switch($name){
            case 'app_id':
            case 'app_key':
            case 'app_secret':
            case 'url':
                $value = $this->$name ?? "";
                break;
            default:
                $value = '';
        }

        return $value;
    }// getAttribute() end

    /**
     * 设置属性值
     *
     * @param string $name
     * @param string $value
     * @param string $attribute 如果 BaiGoSso 的地址与项目不一致，可以进行手动替换
     * @return $this
     */
    public function setAttribute(string $name, string $value, string $attribute = "")
    {
        if (strlen($name) > 0)$name = strtolower($name);

        switch($name){
            case 'app_id':
            case 'app_key':
            case 'app_secret':
            case 'url':
                $this->$name = $value ?? "";
                if (strlen($attribute) > 0) $this->$name = $attribute;
                break;
            case 'uri':
                $this->$name = self::URIS[$value] ?? $attribute;
                break;
        }

        return $this;
    }// setAttribute() end

    /**
     * 验证签名
     *
     * @param string $str       json 字符
     * @param string $sign      签名
     * @param bool   $is_upper  大小写
     * @return bool
     */
    public function verifySign(string $str, string $sign, $is_upper = true): bool
    {
        if ($this->generateSign($str, $is_upper) === $sign) return true;

        return false;
    }// verifySign() end

    /**
     * 生成签名
     *
     * @param string $str       json 格式字符串
     * @param bool   $is_upper  大小写
     * @return string
     */
    public function generateSign(string $str, bool $is_upper = true): string
    {
        $salt = $this->app_key . $this->app_secret;
        $sign = md5($str.$salt);

        if ($is_upper) $sign = strtoupper($sign);

        return $sign;
    }// generateSign() end

    /**
     * 对称解密
     *
     * @param string $str   json 格式字符串
     * @return string
     */
    public function decrypt(string $str)
    {
        return openssl_decrypt($this->base64Decode($str), self::OPENSSL_METHOD, $this->app_key, self::OPENSSL_OPTIONS, $this->app_secret);
    }// decrypt() end

    /**
     * 对称加密
     *
     * @param string $str   json 格式字符串
     * @return string
     */
    public function encrypt(string $str)
    {
        return $this->base64Encode(openssl_encrypt($str, self::OPENSSL_METHOD, $this->app_key, self::OPENSSL_OPTIONS, $this->app_secret));
    }// encrypt() end

    /**
     * base64 编码
     *
     * @param string $str       字符
     * @param bool   $url_safe  是否用 url 安全的形式编码
     * @return string
     */
    public function base64Encode(string $str, bool $url_safe = true):string
    {
        $str = base64_encode($str);
        if ($url_safe) return str_replace(['+', '/', '='], ['-', '_', ''], $str);

        return $str;
    }// base64Encode() end

    /**
     * base64 解码
     *
     * @param string $str       字符
     * @param bool   $url_safe  是否用 url 安全的形式编码
     * @return string
     */
    public function base64Decode(string $str, bool $url_safe = true):string
    {
        if ($url_safe) $str = str_replace(['-', '_'], ['+', '/'], $str);

        $start = strlen($str) % 4;

        if ($start > 0) $str .= substr('====', $start);

        return base64_decode($str);
    }// base64Decode() end

    /**
     * 通过 curl 发送 get 请求
     *
     * @param string $url   请求 url
     */
    public function sendGetRequest(string $url)
    {
        if (10 > strlen($url)) return;

        $curl = curl_init();
        $options = [
            CURLOPT_URL             => $url,
            CURLOPT_RETURNTRANSFER  => true,
            CURLOPT_ENCODING        => '',
            CURLOPT_MAXREDIRS       => 10,
            CURLOPT_TIMEOUT         => 0,
            CURLOPT_FOLLOWLOCATION  => true,
            CURLOPT_HTTP_VERSION    => CURL_HTTP_VERSION_1_1,
            CURLOPT_CUSTOMREQUEST   => 'GET',
        ];

        curl_setopt_array($curl, $options);
        $response = curl_exec($curl);

        curl_close($curl);
        echo $response;
    }// sendGetRequest() end
}